1 /**
2  * Provides the basic manipulation functions of images.
3  * 
4  * This is a submodule of devisualization.image.manipulation.
5  * 
6  * License:
7  *              Copyright Devisualization (Richard Andrew Cattermole) 2014 - 2017.
8  *     Distributed under the Boost Software License, Version 1.0.
9  *        (See accompanying file LICENSE_1_0.txt or copy at
10  *              http://www.boost.org/LICENSE_1_0.txt)
11  */
12 module devisualization.image.manipulation.base;
13 import devisualization.image.interfaces;
14 import devisualization.image.primitives : isImage, ImageColor, isPixelRange;
15 import std.experimental.color : isColor;
16 import stdx.allocator : IAllocator, theAllocator;
17 import std.traits : isPointer;
18 
19 /**
20  * Fills an image storage instance with a single color
21  *
22  * Can be used to initialize an image with a set color.
23  *
24  * Params:
25  *      image   =   The image to fill into
26  *      value   =   The color to fill as
27  *
28  * Returns:
29  *      The image instance for composability
30  *
31  * Examples:
32  * -------------
33  *  SwappableImage!RGB8 image = ...;
34  *  image.fill(RGB8(77, 82, 31));
35  * -------------
36  */
37 ref Image fillOn(Image, Color = ImageColor!Image)(ref Image image, Color value) @nogc @safe if (isImage!Image && is(Image == struct) && !isPointer!Image) {
38 	return *fillOn(&image, value);
39 }
40 
41 // Ditto
42 Image fillOn(Image, Color = ImageColor!Image)(Image image, Color value) @nogc @safe if (isImage!Image) {
43 	foreach(x; 0 .. image.width) {
44 		foreach(y; 0 .. image.height) {
45 			image.setPixel(x, y, value);
46 		}
47 	}
48 	
49 	return image;
50 }
51 
52 ///
53 unittest {
54     import std.experimental.color;
55     enum RGB8 TheColor = RGB8(78, 82, 11);
56 
57     MyTestImage!RGB8 test = new MyTestImage!RGB8(2, 2);
58     test.fillOn(TheColor);
59     
60     assert(test[0, 0] == TheColor);
61     assert(test[0, 1] == TheColor);
62     assert(test[1, 0] == TheColor);
63     assert(test[1, 1] == TheColor);
64 }
65 
66 /**
67  * Fills a range as replacement for an image size.
68  *
69  * Can be used to initialize an image with a set color.
70  *
71  * Params:
72  *      image   =   The image to fill as
73  *      value   =   The color to fill as
74  *
75  * Returns:
76  *      An input range that returns value for each pixel in the image
77  *
78  * Examples:
79  * -------------
80  *  SwappableImage!RGB8 image = ...;
81  *  foreach(pixel; image.fillRange(RGB8(77, 82, 31))) {
82  *      writeln(pixel, " at x:", pixel.x, " y:", pixel.y);
83  *      writeln("\tImage has size width:", pixel.imageWidth, " height:", pixel.imageHeight);
84  *  }
85  * -------------
86  */
87 auto fillRange(Image, Color = ImageColor!Image)(Image image, Color value) @nogc nothrow @safe if (isImage!Image) {
88     return FillRange!Color(image, value);
89 }
90 
91 /**
92  * Fills a range as replacement for an image size.
93  *
94  * Can be used to initialize an image with a set color.
95  *
96  * Params:
97  *      image   =   An input range for color to fill as
98  *      value   =   The color to fill as
99  *
100  * Returns:
101  *      An input range that returns value for each pixel in the image
102  *
103  * Examples:
104  * -------------
105  *  SwappableImage!RGB8 image = ...;
106  *  foreach(pixel; image.fillRange(RGB8(77, 82, 31))) {
107  *      writeln(pixel, " at x:", pixel.x, " y:", pixel.y);
108  *      writeln("\tImage has size width:", pixel.imageWidth, " height:", pixel.imageHeight);
109  *  }
110  * -------------
111  */
112 auto fillRange(IRImage, ET = ElementType!IRImage, Color)(IRImage image, Color value) @nogc nothrow @safe if (isPixelRange!IRImage && is(ET == PixelPoint!Color)) {
113     return FillRange!Color(value, 0, 0, image.front.imageWidth, image.front.imageHeight);
114 }
115 
116 private {
117     import std.range : ElementType;
118 
119     struct FillRange(Color) {
120         private {
121             Color color;
122             size_t x_, y_, image_width, image_height;
123         }
124 
125         this(Image)(Image image, Color value) @nogc nothrow @safe {
126             this.color = value;
127             image_width = image.width;
128             image_height = image.height;
129         }
130 
131         private this(Color value, size_t x, size_t y, size_t width, size_t height) @nogc nothrow @safe {
132             this.color = value;
133             this.x_ = x;
134             this.y_ = y;
135             this.image_width = width;
136             this.image_height = height;
137         }
138 
139         @property {
140             PixelPoint!Color front() @nogc nothrow @safe {
141                 return PixelPoint!Color(color, x_, y_, image_width, image_height);
142             }
143 
144             bool empty() @nogc nothrow @safe {
145                 return x_ >= image_width && y_ >= image_height - 1;
146             }
147 
148             FillRange!Color save() @nogc nothrow @safe {
149                 return FillRange!Color(color, x_, y_, image_width, image_height);
150             }
151         }
152 
153         void popFront() @nogc nothrow @safe {
154             x_++;
155 
156             if (empty()) {
157             } else {
158                 if (x_ == image_width) {
159                     x_ = 0;
160                     y_++;
161                 }
162             }
163         }
164     }
165 }
166 
167 ///
168 unittest {
169     import std.experimental.color;
170     enum RGB8 TheColor = RGB8(78, 82, 11);
171     enum RGB8 TheColor2 = RGB8(6, 93, 11);
172 
173     MyTestImage!RGB8 test = new MyTestImage!RGB8(2, 2);
174     
175     size_t count;
176     foreach(pixel; fillRange(test, TheColor)) {
177         assert(pixel.value == TheColor);
178         count++;
179     }
180     assert(count == 4);
181 
182     count = 0;
183     foreach(pixel; fillRange(test, TheColor).fillRange(TheColor2)) {
184         assert(pixel.value == TheColor2);
185         count++;
186     }
187     assert(count == 4);
188 }
189 
190 /**
191  * Flips an image horiztonally
192  *
193  * Params:
194  *      image   =   The image to flip
195  *
196  * Returns:
197  *      The image for composibility reasons
198  */
199 ref Image flipHorizontal(Image)(ref Image image) if (isImage!Image && is(Image == struct) && !isPointer!Image) {
200 	return *flipHorizontal(&image);
201 }
202 
203 /// Ditto
204 Image flipHorizontal(Image)(Image image) if (isImage!Image) {
205     alias Color = ImageColor!Image;
206 
207     size_t height = image.height;
208     size_t width = image.width;   
209     size_t h2width = width / 2; // is floored as it is an integer not floating point. 
210 
211     foreach(y; 0 .. height) {
212         size_t x2 = width;
213         foreach(x; 0 .. h2width) {
214             x2--;
215             Color temp;
216             temp = image.getPixel(x, y);
217 
218 			image.setPixel(x, y, image.getPixel(x2, y));
219 			image.setPixel(x2, y, temp);
220         }
221     }
222 
223     return image;
224 }
225 
226 ///
227 unittest {
228     import std.experimental.color;
229     MyTestImage!RGB8 test = new MyTestImage!RGB8(2, 2);
230     test.flipHorizontal();
231 }
232 
233 /**
234  * Flips an image horizontally
235  *
236  * Will allocate a new range as the input image.
237  *
238  * Params:
239  *      image       =   The image to flip
240  *      allocator   =   Allocator to allocate the wrapper range for
241  *
242  * Returns:
243  *      An input range that returns value for each pixel in the image
244  *
245  * See_Also:
246  *      rangeOf
247  */
248 
249 auto flipHorizontalRange(Image)(ref Image image, IAllocator allocator=theAllocator()) @safe if (isImage!Image) {
250     return flipHorizontalRange(image.rangeOf(allocator));
251 }
252 
253 /**
254  * Flips an image horizontally
255  *
256  * Params:
257  *      image       =   The image to flip
258  *      allocator   =   The allocator to deallocate the image 
259  *      ptrToFree   =   The point to free upon destructor call
260  *
261  * Returns:
262  *      An input range that returns value for each pixel in the image
263  */
264 auto flipHorizontalRange(IRImage, ET = ElementType!IRImage)(IRImage image) @nogc @safe if (isPixelRange!IRImage) {
265     alias Color = typeof(ET.value);
266 
267     struct Result {
268         private {
269             IRImage input;
270             size_t h2width;
271         }
272 
273         this(IRImage input) @nogc @safe {
274             this.input = input;
275             h2width = input.front.imageWidth / 2;
276         }
277 
278         @property {
279             PixelPoint!Color front() @nogc @safe {
280                 auto got = input.front;
281                 return PixelPoint!Color(got.value, got.imageWidth - got.x, got.y, got.imageWidth, got.imageHeight);
282             }
283 
284             bool empty() @safe {
285                 return input.empty;
286             }
287         }
288 
289         void popFront() @nogc nothrow @safe {
290             input.popFront;
291         }
292     }
293 
294     return Result(image);
295 }
296 
297 ///
298 unittest {
299     import std.experimental.color;
300     enum RGB8 TheColor = RGB8(78, 82, 11);
301 
302     MyTestImage!RGB8 test = new MyTestImage!RGB8(2, 2); 
303     test.fillOn(TheColor);
304 
305     final class Foo {
306         MyTestImage!RGB8 value;
307         alias value this;
308 
309         this(MyTestImage!RGB8 value) {
310             this.value = value;
311         }
312     }
313 
314     size_t count;
315     foreach(pixel; flipHorizontalRange(test)) {
316         assert(pixel.value == TheColor);
317         count++;
318     }
319     assert(count == 4);
320 
321     count = 0;
322     foreach(pixel; flipHorizontalRange(test).flipHorizontalRange()) {
323         assert(pixel.value == TheColor);
324         count++;
325     }
326     assert(count == 4);
327 }
328 
329 /**
330  * Flips an image vertical
331  *
332  * Params:
333  *      image   =   The image to flip
334  *
335  * Returns:
336  *      The image for composibility reasons
337  */
338 ref Image flipVertical(Image)(ref Image image) if (isImage!Image && is(Image == struct) && !isPointer!Image) {
339 	return *flipVertical(&image);
340 }
341 
342 /// Ditto
343 Image flipVertical(Image)(Image image) if (isImage!Image) {
344     alias Color = ImageColor!Image;
345 
346     size_t height = image.height;
347     size_t width = image.width;   
348     size_t h2height = height / 2; // is floored as it is an integer not floating point. 
349 
350     foreach(x; 0 .. width) {
351         size_t y2 = height;
352         foreach(y; 0 .. h2height) {
353             y2--;
354             Color temp;
355 			temp = image.getPixel(x, y);
356 
357 			image.setPixel(x, y, image.getPixel(x, y2));
358 			image.setPixel(x, y2, temp);
359         }
360     }
361 
362     return image;
363 }
364 
365 ///
366 unittest {
367     import std.experimental.color;
368     MyTestImage!RGB8 test = new MyTestImage!RGB8(2, 2);
369     test.flipVertical();
370 }
371 
372 /**
373  * Flips an image vertical
374  *
375  * Will allocate a new range as the input image.
376  *
377  * Params:
378  *      image       =   The image to flip
379  *      allocator   =   Allocator to allocate the wrapper range for
380  *
381  * Returns:
382  *      An input range that returns value for each pixel in the image
383  *
384  * See_Also:
385  *      rangeOf
386  */
387 auto flipVerticalRange(Image)(ref Image image, IAllocator allocator=theAllocator()) @safe if (isImage!Image) {
388     return flipVerticalRange(image.rangeOf(allocator));
389 }
390 
391 /**
392  * Flips an image horizontally
393  *
394  * Params:
395  *      image   =   The image to flip
396  *
397  * Returns:
398  *      An input range that returns value for each pixel in the image
399  */
400 auto flipVerticalRange(IRImage, ET = ElementType!IRImage)(IRImage image) @nogc @safe if (isPixelRange!IRImage) {
401     alias Color = typeof(ET.value);
402 
403     struct Result {
404         private {
405             IRImage input;
406         }
407 
408         this(IRImage input) @nogc @safe {
409             this.input = input;
410         }
411 
412         @property {
413             PixelPoint!Color front() @nogc @safe {
414                 auto got = input.front;
415                 return PixelPoint!Color(got.value, got.x, got.imageHeight - got.y, got.imageWidth, got.imageHeight);
416             }
417 
418             bool empty() @safe {
419                 return input.empty;
420             }
421 
422             static if (__traits(hasMember, IRImage, "save")) {
423                 auto save() @nogc @safe {
424                     return Result(input.save());
425                 }
426             }
427         }
428 
429         void popFront() @nogc nothrow @safe {
430             input.popFront;
431         }
432     }
433 
434     return Result(image);
435 }
436 
437 ///
438 unittest {
439     import std.experimental.color;
440     enum RGB8 TheColor = RGB8(78, 82, 11);
441 
442     MyTestImage!RGB8 test = new MyTestImage!RGB8(2, 2); 
443     test.fillOn(TheColor);
444 
445     final class Foo {
446         MyTestImage!RGB8 value;
447         alias value this;
448 
449         this(MyTestImage!RGB8 value) {
450             this.value = value;
451         }
452     }
453 
454     size_t count;
455     foreach(pixel; flipVerticalRange(test)) {
456         assert(pixel.value == TheColor);
457         count++;
458     }
459     assert(count == 4);
460 
461     count = 0;
462     foreach(pixel; flipVerticalRange(test).flipVerticalRange()) {
463         assert(pixel.value == TheColor);
464         count++;
465     }
466     assert(count == 4);
467 }